﻿using System;
using System.Collections.Generic;
using System.Text;

using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;

using RexToy;

namespace ScriptXCompiler
{
    public partial class Translator
    {
        private bool IsAssignExpression(BinaryExpressionSyntax syntax)
        {
            switch (syntax.Kind)
            {
                case SyntaxKind.AssignExpression:
                case SyntaxKind.AddAssignExpression:
                case SyntaxKind.AndAssignExpression:
                case SyntaxKind.DivideAssignExpression:
                case SyntaxKind.ExclusiveOrAssignExpression:
                case SyntaxKind.LeftShiftAssignExpression:
                case SyntaxKind.ModuloAssignExpression:
                case SyntaxKind.MultiplyAssignExpression:
                case SyntaxKind.OrAssignExpression:
                case SyntaxKind.RightShiftAssignExpression:
                case SyntaxKind.SubtractAssignExpression:
                    return true;

                default:
                    return false;
            }
        }

        private string DecomposeOperator(BinaryExpressionSyntax syntax)
        {
            switch (syntax.Kind)
            {
                case SyntaxKind.AddAssignExpression:
                    return "+";

                case SyntaxKind.AndAssignExpression:
                    return "&";

                case SyntaxKind.DivideAssignExpression:
                    return "/";

                case SyntaxKind.ExclusiveOrAssignExpression:
                    return "^";

                case SyntaxKind.LeftShiftAssignExpression:
                    return "<<";

                case SyntaxKind.ModuloAssignExpression:
                    return "%";

                case SyntaxKind.MultiplyAssignExpression:
                    return "*";

                case SyntaxKind.OrAssignExpression:
                    return "|";

                case SyntaxKind.RightShiftAssignExpression:
                    return ">>";

                case SyntaxKind.SubtractAssignExpression:
                    return "-";

                default:
                    this.AppendCompileIssue(syntax, IssueType.Error, IssueId.UnknownCompositeOperator, syntax.Kind);
                    return string.Empty;
            }
        }

        private bool IsPropertyAccess(ExpressionSyntax syntax)
        {
            MemberAccessExpressionSyntax maes = syntax as MemberAccessExpressionSyntax;
            if (maes == null)
                return false;
            var info = _semanticModel.GetSymbolInfo(maes);

            if (info.Symbol == null)
                return false;

            return info.Symbol.Kind == SymbolKind.Property;
        }

        private bool IsPartOfAssignment(SyntaxNode node)
        {
            var bes = node.Parent as BinaryExpressionSyntax;
            if (bes != null && IsAssignExpression(bes))
            {
                return true;
            }
            var evcs = node.Parent as EqualsValueClauseSyntax;
            if (evcs != null)
            {
                return true;
            }
            return false;
        }

        private string MakeInterfacesList(ClassDeclarationSyntax syntax)
        {
            var interfaces = _semanticModel.GetDeclaredSymbol(syntax).Interfaces;

            StringBuilder str = new StringBuilder("[");
            foreach (var info in interfaces)
            {
                if (!info.IsScriptSymbol())
                {
                    this.AppendCompileIssue(syntax, IssueType.Error, IssueId.UseNonScript, info);
                }
                str.AppendFormat("{0}, ", info.GetTypeSymbolName());
            }
            if (interfaces.Count > 0)
                str.RemoveEnd(", ");
            str.Append("]");
            return str.ToString();
        }

        private string MakeParametersList(ParameterListSyntax syntax)
        {
            var args = syntax.Parameters;
            StringBuilder str = new StringBuilder();
            if (args.Count > 0)
            {
                var fstArg = args[0];
                if (fstArg.IsExtensionParameter())
                    this.AppendCompileIssue(syntax, IssueType.Error, IssueId.ExtensionMethodNotSupport);

                foreach (var arg in args)
                {
                    var info = _semanticModel.GetDeclaredSymbol(arg);
                    if (!info.Type.IsScriptSymbol())
                    {
                        this.AppendCompileIssue(syntax, IssueType.Error, IssueId.UseNonScript, info.Type);
                    }

                    if (arg.Default != null)
                    {
                        this.AppendCompileIssue(syntax, IssueType.Error, IssueId.DefaultParamNotSupport);
                    }

                    str.Append(arg.GetParameterSymbolName(_semanticModel, _dict)).Append(", ");
                }
                str.RemoveEnd(", ");
            }
            return str.ToString();
        }

        private void MakeLocalVariableList(VariableDeclarationSyntax syntax)
        {
            int count = 0;
            foreach (var v in syntax.Variables)
            {
                if (count == 0)
                {
                    _output.Write(v.Identifier.ValueText);
                }
                else
                {
                    _output.Write(", ");
                    _output.Write(v.Identifier.ValueText);
                }

                if (v.Initializer != null)
                {
                    _output.Write(" = ");
                    this.Visit(v.Initializer);
                }
                count++;
            }
        }

        private void MakeArgumentsList(SeparatedSyntaxList<ArgumentSyntax> syntaxList)
        {
            int count = 0;
            foreach (var arg in syntaxList)
            {
                if (arg.NameColon != null)
                {
                    this.AppendCompileIssue(arg, IssueType.Error, IssueId.NamedArgumentNotSupport);
                }

                this.VisitExpression(arg.Expression);
                count++;
                if (count != syntaxList.Count)
                    _output.Write(", ");
            }
        }

        private void MakeExpressionList(SeparatedSyntaxList<ExpressionSyntax> list)
        {
            int count = 0;
            foreach (var expr in list)
            {
                if (count != 0)
                    _output.Write(", ");

                this.VisitExpression(expr);
                count++;
            }
        }

        private string TranslateUnaryOperator(SyntaxToken token)
        {
            switch (token.Kind)
            {
                case SyntaxKind.PlusPlusToken:
                    return " + 1";

                case SyntaxKind.MinusMinusToken:
                    return " - 1";

                default:
                    this.AppendCompileIssue(token, IssueType.Error, IssueId.UnknownUnaryOperator);
                    return string.Empty;
            }
        }

        private bool IsPartOfDynamic(ExpressionSyntax syntax)
        {
            var maes = syntax as MemberAccessExpressionSyntax;
            if (maes != null)
            {
                var info = _semanticModel.GetTypeInfo(maes.Expression);

                //Extend:Should not occur, doubt it's Roslyn's bug while compile object create initialize syntax.
                if (info.Type == null)
                    return false;

                if (info.Type.Kind == SymbolKind.DynamicType)
                    return true;

                if (info.Type.Kind == SymbolKind.ErrorType)
                {
                    if (maes.Expression.Kind == SyntaxKind.MemberAccessExpression)
                    {
                        var expr = maes.Expression as MemberAccessExpressionSyntax;
                        return IsPartOfDynamic(expr.Expression);
                    }

                    if (maes.Expression.Kind == SyntaxKind.InvocationExpression)
                    {
                        var expr = maes.Expression as InvocationExpressionSyntax;
                        return IsPartOfDynamic(expr.Expression);
                    }
                }

                return false;
            }
            else
            {
                var info = _semanticModel.GetTypeInfo(syntax);
                if (info.Type != null && info.Type.Kind == SymbolKind.DynamicType)
                    return true;
            }

            return false;
        }

        private void VisitExpression(ExpressionSyntax syntax)
        {
            var constant = _semanticModel.GetConstantValue(syntax);
            var info = _semanticModel.GetSymbolInfo(syntax);

            if (info.Symbol != null && info.Symbol.IsEvalCandidate())
            {
                if (constant.HasValue)
                {
                    if (info.Symbol.HasEvalSuggestValue())
                    {
                        _output.Write(info.Symbol.ReadEvalSuggestValue().ToString());
                    }
                    else
                    {
                        if (constant.Value == null)
                            _output.Write("null");
                        else
                        {
                            if (constant.Value is string)
                            {
                                _output.Write("\"" + constant.Value.ToString() + "\"");
                            }
                            else
                            {
                                _output.Write(constant.Value.ToString());
                            }
                        }
                    }
                }
                else
                {
                    this.AppendCompileIssue(syntax, IssueType.Error, IssueId.NotConstant, syntax);
                }
            }
            else
            {
                Visit(syntax);
            }
        }

        private void AppendCompensateSemicolon(StatementSyntax syntax)
        {
            if (!(syntax.Kind == SyntaxKind.Block))
            {
                if (syntax.Kind == SyntaxKind.ExpressionStatement || syntax.Kind == SyntaxKind.ThrowStatement || syntax.Kind == SyntaxKind.ReturnStatement)
                    _output.WriteLine(";");
            }
        }

        private void OutputNativeMethod(BlockSyntax node)
        {
            var txt = node.GetText().ToString().Trim();//Note: Leading/Trailing white space
            txt = txt.UnBracketing(StringPair.CurlyBracket).Trim();
            txt = txt.UnBracketing(StringPair.Create("/*", "*/"));
            string[] lines = txt.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);

            _output.WriteLineWithIdent(this.CurrLineIdent, "//**Js Native code start");
            _output.WriteLineWithIdent(this.CurrLineIdent, txt);
            _output.WriteLineWithIdent(this.CurrLineIdent, "//**Js Native code end");
        }

        private void GenerateMethodCode(SimpleNameSyntax node, Symbol symbol)
        {
            if (node.Parent.Kind != SyntaxKind.MemberAccessExpression)
            {
                if (symbol.IsStatic)
                {
                    _output.Write("{0}.", symbol.ContainingType.GetTypeSymbolName());
                }
                else
                {
                    _output.Write("this.");
                }
            }
            _output.Write(symbol.GetMemberSymbolName());
        }

        //Extend:this method exist because current version API can not support object create initailize syntax, should remove in future!
        private string BindSemanticModel(ObjectCreationExpressionSyntax syntax, SyntaxToken identifier)
        {
            var info = _semanticModel.GetTypeInfo(syntax.Type);

            string result = _BindTypeMember(syntax, info.Type, identifier.ValueText);
            if (string.IsNullOrEmpty(result))
            {
                this.AppendCompileIssue(syntax, IssueType.Error, IssueId.SemanticBind);
            }
            return result;
        }

        private string _BindTypeMember(ObjectCreationExpressionSyntax syntax, TypeSymbol symbol, string memberName)
        {
            var members = symbol.GetMembers(memberName);
            switch (members.Count)
            {
                case 1:
                    var member = members[0];
                    if (member.Kind == SymbolKind.Property)
                        this.AppendCompileIssue(syntax, IssueType.Warning, IssueId.ObjectInitializeSyntaxSetProperty);
                    return member.GetMemberSymbolName();

                case 0:
                    var t = symbol.BaseType;
                    if (t != null && !(t.Kind == SymbolKind.ErrorType))
                    {
                        return _BindTypeMember(syntax, t, memberName);
                    }
                    break;
            }
            return null;
        }

        private bool IsGlobalHolder(ExpressionSyntax syntax)
        {
            var info = _semanticModel.GetSymbolInfo(syntax);
            if (info.Symbol != null && info.Symbol.Kind == SymbolKind.NamedType)
            {
                var name = info.Symbol.GetTypeSymbolName();
                return string.IsNullOrEmpty(name);
            }

            return false;
        }

        private void SetJsonNameQuote(ClassDeclarationSyntax syntax)
        {
            Symbol symbol = _semanticModel.GetDeclaredSymbol(syntax);
            var result = symbol.GetJsonQuote();
            _jsonNameFlag.SetClassLevel(result);
        }

        private void SetJsonNameQuote(MethodDeclarationSyntax syntax)
        {
            Symbol symbol = _semanticModel.GetDeclaredSymbol(syntax);
            var result = symbol.GetJsonQuote();
            _jsonNameFlag.SetMethodLevel(result);
        }

        private void AppendCompileIssue(SyntaxNode node, IssueType type, IssueId id, params object[] args)
        {
            var issue = CompileIssue.Create(this._semanticModel.SyntaxTree, node, type, id, args);
            _aggregator.AppendIssue(issue);
        }

        private void AppendCompileIssue(SyntaxToken token, IssueType type, IssueId id, params object[] args)
        {
            var issue = CompileIssue.Create(this._semanticModel.SyntaxTree, token, type, id, args);
            _aggregator.AppendIssue(issue);
        }
    }
}
